package com.gotloop.dao;

import java.lang.reflect.ParameterizedType;
import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Query;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jdo.PersistenceManagerFactoryUtils;
import org.springframework.stereotype.Repository;

import com.google.appengine.api.datastore.KeyFactory;

/**
 * Generic superclass for every Data Access Object.
 * @author jibhaine
 *
 * @param <E> The Entity managed by the child DAO class
 * @param <ID> The Entity primary key
 */
@Repository
public abstract class AbstractDAOImpl<E, ID> implements AbstractDAO<E, ID> {
	
	/**
	 * Logger for BaseDAOImpl.
	 */
	private static final Logger LOG = LoggerFactory.getLogger(AbstractDAOImpl.class);
	
	/**
	 * JDO PersistenceManager to access data.
	 */
	private PersistenceManagerFactory pmf;
	
	/**
	 * Entity Class managed by the DAO implementation.
	 */
	private Class<E> entityClass;
	
	/**
	 * Set accessor for persistenceManagerFactory.
	 * @param pPMF the persistenceManagerFactory to set.
	 */
	@Autowired
	public void setPersistenceManagerFactory(PersistenceManagerFactory pPMF) {
		this.pmf = pPMF;
	}
	
	/**
	 * Method to retrieve the PersistenceManager (to work with spring transactions).
	 * @return a Persistence Manager instance
	 */
	protected PersistenceManager getPersistenceManager() {
		PersistenceManager manager = PersistenceManagerFactoryUtils.getPersistenceManager(pmf, true);
		manager.setDetachAllOnCommit(true);
		manager.setCopyOnAttach(true);
		return manager;
	}  
	/**
	 * Generic class used to get entity name in generic requests.
	 * @return The Entity class.
	 */
	@SuppressWarnings("unchecked")
	protected Class<E> getEntityClass() {
		if (entityClass == null) {
            ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
            entityClass = (Class<E>) type.getActualTypeArguments()[0];
		}
		return entityClass;
	}

	@Override
	public E save(E entity) {
		LOG.trace("saving entity {}", entity);
		PersistenceManager manager = getPersistenceManager();
		manager.makePersistent(entity);
		manager.close();
		LOG.trace("saved entity {}", entity);
		return entity;
	}
	
	@Override
	public List<E> saveAll(List<E> entityList) {
		LOG.debug("Start");
		PersistenceManager manager = getPersistenceManager();
		for (E entity : entityList) {
			LOG.trace("saving entity {}", entity);
			manager.makePersistent(entity);
			LOG.trace("saved entity {}", entity);
		}
		manager.close();
		return entityList;
	}

	@Override
	public void update(E entity) {
		PersistenceManager manager = getPersistenceManager();
		manager.refresh(entity);
		manager.close();
	}
	
	@Override
	public void delete(E entity) {
		PersistenceManager manager = getPersistenceManager();
		manager.deletePersistent(entity);
		manager.close();
	}

	@Override
	@SuppressWarnings("unchecked")
	public List<E> getAll() {
		LOG.debug("getAll : {}", getEntityClass().getName());
		
		List<E> entityList = null;
		PersistenceManager manager = getPersistenceManager();
		try {
			Query query = manager.newQuery(getEntityClass());
			entityList = (List<E>) manager.detachCopyAll((List<E>) query.execute());
		} catch (Exception exp) {
			LOG.error("Exception {}", exp.getLocalizedMessage());
		} finally {
			if (manager.currentTransaction().isActive()) {
				manager.currentTransaction().commit();
			}
			manager.close();
		}
		LOG.trace("getAll : {}", entityList);
		return entityList;
	}

	@Override
	public E getById(ID primaryKey) {
		LOG.debug("getByID : {}", primaryKey);
		PersistenceManager manager = getPersistenceManager();
		E entity;
		if (primaryKey instanceof String) {
			entity = manager.getObjectById(
					getEntityClass(), 
					KeyFactory.createKey(
							getEntityClass().getSimpleName(),
							(String) primaryKey)
					);
		} else {
			entity = manager.getObjectById(getEntityClass(), primaryKey);
		}
		if (entity != null) {
			entity = (E) manager.detachCopy(entity);
		}
		LOG.trace("getByID : {}", entity);
		return entity;
	}

}
